HTTP Requests in Flutter
HTTP requests are an essential part of modern Flutter applications because they allow an app to communicate with remote servers, REST APIs, databases, authentication services, and other backend systems. Flutter commonly uses the http package to perform HTTP requests such as GET, POST, PUT, PATCH, and DELETE.
The http package provides a simple and widely used approach for making cross-platform HTTP requests in Flutter applications.
1. What Are HTTP Requests?
HTTP stands for Hypertext Transfer Protocol. It is a communication protocol used to transfer data between a client and a server.
In a Flutter application:
- The Flutter application acts as the client.
- The backend or API acts as the server.
- The client creates and sends an HTTP request.
- The server receives and processes the request.
- The server sends an HTTP response.
- The Flutter application processes the response and updates the UI.
Basic HTTP Flow
Flutter App
↓
HTTP Request
↓
Backend / API Server
↓
HTTP Response
↓
Flutter App
↓
Process Data
↓
Display Data
2. Why Are HTTP Requests Used in Flutter?
HTTP requests allow Flutter applications to work with data that is stored or processed outside the application.
Common examples include:
- User registration and login
- Fetching products
- Displaying news articles
- Loading user profiles
- Submitting forms
- Uploading application data
- Updating records
- Deleting records
- Searching remote data
- Fetching weather information
- Communicating with payment or business APIs
Real-World Example
Suppose a shopping application needs to display products. The products may be stored on a backend server rather than inside the Flutter application.
Flutter Application
↓
GET /products
↓
Backend API
↓
Database
↓
JSON Response
↓
Flutter Application
↓
Product List
3. HTTP Requests and REST APIs
Most Flutter applications communicate with REST APIs using HTTP. A REST API usually exposes different endpoints for different operations.
| HTTP Method |
Typical Purpose |
Example |
| GET |
Retrieve data |
Get a list of products |
| POST |
Create data |
Create a new user |
| PUT |
Update or replace data |
Update a complete user record |
| PATCH |
Partially update data |
Update only the user's name |
| DELETE |
Delete data |
Delete a product |
REST API Example
GET /api/users
POST /api/users
GET /api/users/10
PUT /api/users/10
PATCH /api/users/10
DELETE /api/users/10
4. Adding the HTTP Package
The Flutter http package provides convenient methods for making HTTP requests.
Step 1: Add the Package
flutter pub add http
Step 2: Import the Package
import 'package:http/http.dart' as http;
After importing the package, you can use methods such as http.get(), http.post(), http.put(), http.patch(), and http.delete().
5. Internet Permission on Android
Android applications that access the internet need the appropriate internet permission in the Android manifest.
The permission is normally declared in:
android/app/src/main/AndroidManifest.xml
For applications that require internet access, the manifest can contain:
<uses-permission android:name="android.permission.INTERNET" />
Always verify the Android project configuration when an application cannot access the network.
6. Understanding Uri
An HTTP request needs a URL that identifies the server resource. In Dart, URLs are represented using the Uri class.
Using Uri.parse()
final uri = Uri.parse(
'https://example.com/api/users',
);
Using Uri.https()
final uri = Uri.https(
'example.com',
'/api/users',
);
Uri.https() is especially useful when constructing URLs programmatically because query parameters can be supplied separately.
final uri = Uri.https(
'example.com',
'/api/users',
{
'page': '1',
'limit': '10',
},
);
7. HTTP GET Request
A GET request is used to retrieve data from a server.
Basic GET Example
import 'package:http/http.dart' as http;
Future<http.Response> fetchUsers() async {
final response = await http.get(
Uri.parse('https://example.com/api/users'),
);
print(response.statusCode);
print(response.body);
return response;
}
The http.get() method performs an asynchronous HTTP GET request and returns a Future containing an http.Response.
GET Request Flow
Flutter App
↓
http.get()
↓
GET /api/users
↓
Server
↓
HTTP Response
↓
response.body
↓
JSON Parsing
↓
Dart Model
↓
UI
8. Understanding Future
HTTP requests take time because the application must communicate with a remote server. Dart represents the result of an asynchronous operation using a Future.
Future<http.Response> fetchData() {
return http.get(
Uri.parse('https://example.com/api/data'),
);
}
A Future represents a value that will become available later or an error if the asynchronous operation fails.
Future Lifecycle
Future Started
↓
Waiting for Network
↓
Server Responds
↓
Future Completes
↓
Result Available
9. Using async and await
The async and await keywords make asynchronous HTTP code easier to read and understand.
Future<void> fetchData() async {
final response = await http.get(
Uri.parse('https://example.com/api/data'),
);
print(response.body);
}
The async keyword marks a function as asynchronous, while await waits for the Future to complete before continuing.
Without await
final responseFuture = http.get(
Uri.parse('https://example.com/api/data'),
);
With await
final response = await http.get(
Uri.parse('https://example.com/api/data'),
);
The second approach allows the code to work directly with the completed response inside the asynchronous function.
10. Understanding HTTP Response
An HTTP response contains information returned by the server.
Important properties include:
statusCode – HTTP status code returned by the server.
body – Response content, commonly JSON.
headers – Metadata returned by the server.
request – Information about the request when available.
final response = await http.get(
Uri.parse('https://example.com/api/users'),
);
print(response.statusCode);
print(response.body);
print(response.headers);
Response Structure
HTTP Response
├── statusCode
├── headers
├── body
└── request information
11. Checking Status Codes
Always check the response status code before assuming that a request was successful.
if (response.statusCode == 200) {
print('Request successful');
} else {
print('Request failed');
}
| Status Code |
Meaning |
| 200 |
OK / successful request |
| 201 |
Created |
| 204 |
No Content |
| 400 |
Bad Request |
| 401 |
Unauthorized |
| 403 |
Forbidden |
| 404 |
Not Found |
| 500 |
Internal Server Error |
Why Status Codes Matter
A request can complete at the network level but still represent an application-level error. Checking statusCode allows the application to distinguish successful and unsuccessful responses.
12. Handling JSON Responses
REST APIs commonly return data in JSON format. Dart provides the dart:convert library for encoding and decoding JSON.
import 'dart:convert';
Suppose the server returns:
{
"id": 1,
"name": "Rahul",
"email": "[email protected]"
}
You can decode it using jsonDecode().
final data = jsonDecode(response.body);
print(data['name']);
JSON Flow
HTTP Response
↓
response.body
↓
jsonDecode()
↓
Dart Map / List
↓
Dart Model
↓
Flutter UI
13. Creating a Dart Model
Instead of working with raw JSON throughout the application, it is better to convert API data into Dart model objects.
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
Why Use Models?
- Provides structured data.
- Improves code readability.
- Reduces repeated JSON access.
- Makes application code easier to maintain.
- Creates a clear boundary between API data and UI code.
14. Converting JSON to a Dart Object
Future<User> fetchUser() async {
final response = await http.get(
Uri.parse('https://example.com/api/users/1'),
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
return User.fromJson(json);
}
throw Exception('Failed to load user');
}
This approach separates the raw API response from the rest of the application and makes the data easier to work with.
15. HTTP POST Request
A POST request is generally used to send data to a server and create a new resource.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> createUser() async {
final response = await http.post(
Uri.parse('https://example.com/api/users'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'name': 'Rahul',
'email': '[email protected]',
}),
);
print(response.statusCode);
print(response.body);
}
The jsonEncode() function converts Dart data into a JSON string before it is sent in the request body.
POST Request Flow
Dart Object / Map
↓
jsonEncode()
↓
JSON Request Body
↓
POST Request
↓
Server
↓
Response
16. HTTP Headers
HTTP headers provide additional information about a request or response.
Common headers include:
Content-Type
Accept
Authorization
User-Agent
Example
final response = await http.get(
Uri.parse('https://example.com/api/users'),
headers: {
'Accept': 'application/json',
},
);
17. Sending JSON Headers
When sending JSON data, the server commonly needs to know that the request body contains JSON.
headers: {
'Content-Type': 'application/json',
}
Complete Example
final response = await http.post(
Uri.parse('https://example.com/api/users'),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json',
},
body: jsonEncode({
'name': 'Amit',
}),
);
18. HTTP PUT Request
PUT is commonly used to update or replace an existing resource.
Future<void> updateUser() async {
final response = await http.put(
Uri.parse('https://example.com/api/users/1'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'name': 'Amit Sharma',
'email': '[email protected]',
}),
);
if (response.statusCode == 200) {
print('User updated');
} else {
print('Update failed');
}
}
19. HTTP PATCH Request
PATCH is commonly used when only part of an existing resource needs to be updated.
final response = await http.patch(
Uri.parse('https://example.com/api/users/1'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'name': 'New Name',
}),
);
The exact support and expected behavior of PATCH depends on the backend API.
PUT vs PATCH
| PUT |
PATCH |
| Commonly used for full replacement or update. |
Commonly used for partial updates. |
| May send the complete resource. |
Usually sends only changed fields. |
20. HTTP DELETE Request
DELETE is used to remove a resource from a server.
Future<void> deleteUser(String id) async {
final response = await http.delete(
Uri.parse('https://example.com/api/users/$id'),
);
if (response.statusCode == 200 ||
response.statusCode == 204) {
print('User deleted');
} else {
print('Delete failed');
}
}
21. Query Parameters
Query parameters are values added to the URL after a ?. They are commonly used for search, filtering, sorting, and pagination.
Using Uri.https()
final uri = Uri.https(
'example.com',
'/api/users',
{
'page': '1',
'limit': '10',
'search': 'rahul',
},
);
final response = await http.get(uri);
Example URL
https://example.com/api/users?page=1&limit=10&search=rahul
Using Uri helps safely construct URLs and handle query parameter encoding.
22. Path Parameters
A path parameter is part of the URL path and is commonly used to identify a particular resource.
final userId = 10;
final response = await http.get(
Uri.parse(
'https://example.com/api/users/$userId',
),
);
Here, 10 identifies the requested user.
Path Parameter vs Query Parameter
| Type |
Example |
Typical Purpose |
| Path Parameter |
/users/10 |
Identify a specific resource |
| Query Parameter |
/users?page=1 |
Filter, search, sort, or paginate |
23. Authentication with HTTP Requests
Many APIs require authentication. A common approach is to send an authorization token in the HTTP headers.
final response = await http.get(
Uri.parse('https://example.com/api/profile'),
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
},
);
Authentication Flow
User Login
↓
Authentication Server
↓
Access Token
↓
Flutter Application
↓
Authorization Header
↓
Protected API
↓
Authenticated Response
Important Security Rule
Do not hard-code private API keys, passwords, or sensitive credentials directly into a production Flutter application when they should remain secret. Client applications can be inspected, so sensitive secrets should generally be protected on the server side.
24. Handling HTTP Exceptions
Network requests can fail because of connectivity problems, invalid URLs, server errors, timeouts, or other conditions. Use try-catch to handle exceptions.
Future<void> fetchData() async {
try {
final response = await http.get(
Uri.parse('https://example.com/api/data'),
);
if (response.statusCode == 200) {
print(response.body);
} else {
print('Server returned ${response.statusCode}');
}
} catch (e) {
print('Request failed: $e');
}
}
Error Handling Flow
HTTP Request
↓
Try Operation
↓
Success? ── Yes ──→ Process Response
│
No
↓
Catch Exception
↓
Show Error / Retry
25. HTTP Request Timeout
A timeout prevents the application from waiting indefinitely for a response.
final response = await http
.get(Uri.parse('https://example.com/api/data'))
.timeout(
const Duration(seconds: 10),
);
You can handle timeout errors using TimeoutException or a general exception handler.
Example
try {
final response = await http
.get(Uri.parse('https://example.com/api/data'))
.timeout(const Duration(seconds: 10));
print(response.body);
} catch (e) {
print('Request failed or timed out: $e');
}
26. Loading, Success, Empty, and Error States
A production Flutter application should clearly handle different states while communicating with an API.
| State |
Example UI |
| Loading |
CircularProgressIndicator |
| Success |
Display API data |
| Empty |
No data available message |
| Error |
Error message and retry button |
Typical UI State Flow
Initial
↓
Loading
↓
Success → Display Data
│
├── Empty → Display Empty State
│
└── Error → Display Error + Retry
27. Using FutureBuilder with HTTP Requests
FutureBuilder can be used to build UI based on the state of a Future.
Future<String> fetchData() async {
final response = await http.get(
Uri.parse('https://example.com/api/message'),
);
if (response.statusCode == 200) {
return response.body;
}
throw Exception('Failed to load data');
}
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
}
if (snapshot.hasData) {
return Center(
child: Text(snapshot.data!),
);
}
return const Center(
child: Text('No data available'),
);
},
);
}
}
Note: For repeated rebuild scenarios, be careful about creating the Future directly inside build(). Store the Future when appropriate.
28. Avoid Making API Calls Directly Inside build()
Calling an HTTP method directly inside build() can cause repeated requests because Flutter may rebuild widgets multiple times.
Instead, initialize the Future once when appropriate.
late Future<String> dataFuture;
@override
void initState() {
super.initState();
dataFuture = fetchData();
}
The Future can then be passed to FutureBuilder without recreating the network request on every rebuild.
29. Creating an API Service Class
As an application becomes larger, HTTP requests should be separated from UI code.
class ApiService {
final String baseUrl = 'https://example.com/api';
Future<http.Response> getUsers() async {
return http.get(
Uri.parse('$baseUrl/users'),
);
}
Future<http.Response> createUser(
String name,
String email,
) async {
return http.post(
Uri.parse('$baseUrl/users'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'name': name,
'email': email,
}),
);
}
}
Why Use an API Service?
- Keeps networking logic separate from widgets.
- Improves code organization.
- Makes methods reusable.
- Makes testing easier.
- Allows common configuration to be centralized.
30. Recommended Project Structure
A larger Flutter application can separate API, models, repositories, and UI code.
lib/
├── models/
│ └── user.dart
├── services/
│ └── api_service.dart
├── repositories/
│ └── user_repository.dart
├── screens/
│ └── users_screen.dart
├── widgets/
│ └── user_card.dart
└── main.dart
This separation makes code easier to maintain, test, and reuse.
Responsibility of Each Layer
| Layer |
Responsibility |
| Model |
Represents structured application data. |
| Service |
Communicates with external APIs. |
| Repository |
Provides an abstraction over data sources. |
| Screen |
Displays the application UI. |
| Widget |
Provides reusable UI components. |
31. Repository Pattern
A repository can act as a layer between the UI or ViewModel and the API service.
class UserRepository {
final ApiService apiService;
UserRepository(this.apiService);
Future<List<User>> getUsers() async {
final response = await apiService.getUsers();
if (response.statusCode != 200) {
throw Exception('Failed to load users');
}
final List<dynamic> data = jsonDecode(response.body);
return data
.map((item) => User.fromJson(item))
.toList();
}
}
Repository Flow
UI
↓
ViewModel / State Layer
↓
Repository
↓
ApiService
↓
HTTP API
↓
Server
32. HTTP Requests with MVVM
HTTP requests can be organized using an MVVM architecture, where the Model handles data structures, the View displays the UI, and the ViewModel manages application state and connects the UI with the data layer.
View
↓
ViewModel
↓
Repository / Model
↓
HTTP API
↓
Server
This separation helps keep networking code away from presentation code and makes the application easier to maintain.
33. Complete GET Request Example
import 'dart:convert';
import 'package:http/http.dart' as http;
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
Future<User> fetchUser() async {
final uri = Uri.parse(
'https://example.com/api/users/1',
);
final response = await http.get(
uri,
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return User.fromJson(data);
}
throw Exception('Failed to load user');
}
GET Processing Flow
GET Request
↓
HTTP Response
↓
Check statusCode
↓
jsonDecode()
↓
User.fromJson()
↓
User Object
↓
UI
34. Complete POST Request Example
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> createUser() async {
final uri = Uri.parse(
'https://example.com/api/users',
);
final response = await http.post(
uri,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({
'name': 'Neha',
'email': '[email protected]',
}),
);
if (response.statusCode == 201) {
print('User created successfully');
print(response.body);
} else {
print('Failed to create user');
}
}
35. Sending Form Data
Not every API expects JSON. Some APIs may require form-encoded data or multipart requests. Always follow the API documentation for the expected content type and request format.
For example, URL-encoded data can be represented using appropriate request headers and encoded values.
Conceptual Flow
Flutter Form
↓
Form Values
↓
Encode According to API
↓
HTTP Request
↓
Backend
36. Sending File Data
Applications such as profile editors, document managers, and social media apps may need to upload files. The http package supports multipart requests through MultipartRequest.
final request = http.MultipartRequest(
'POST',
Uri.parse('https://example.com/api/upload'),
);
request.files.add(
await http.MultipartFile.fromPath(
'file',
filePath,
),
);
final response = await request.send();
print(response.statusCode);
Multipart Upload Flow
Selected File
↓
MultipartFile
↓
MultipartRequest
↓
POST /upload
↓
Server
↓
Upload Response
37. Pagination
APIs often return large datasets in multiple pages rather than sending thousands of records at once.
Example URL
https://example.com/api/products?page=1&limit=20
Flutter Example
final uri = Uri.https(
'example.com',
'/api/products',
{
'page': '1',
'limit': '20',
},
);
final response = await http.get(uri);
Pagination Flow
Page 1
↓
Load More
↓
Page 2
↓
Load More
↓
Page 3
↓
Continue Until No More Data
38. Search API Requests
Search functionality can send the search keyword to an API.
final uri = Uri.https(
'example.com',
'/api/products/search',
{
'query': 'laptop',
},
);
final response = await http.get(uri);
The API can use the query value to search its available data and return matching records.
39. Pull-to-Refresh
Flutter applications often allow users to refresh API data using RefreshIndicator.
RefreshIndicator(
onRefresh: () async {
await fetchUsers();
},
child: ListView(
children: const [
ListTile(title: Text('User 1')),
ListTile(title: Text('User 2')),
],
),
)
Pull-to-Refresh Flow
User Pulls Down
↓
RefreshIndicator
↓
API Request
↓
New Data
↓
Update UI
40. Retry Failed Requests
A retry mechanism can allow users to request the data again after a temporary network failure.
ElevatedButton(
onPressed: () {
setState(() {
dataFuture = fetchData();
});
},
child: const Text('Retry'),
)
A retry button is especially useful when the original request fails because of a temporary connectivity problem.
41. API Caching
Caching means storing previously retrieved data so that it can sometimes be reused without making another network request.
Benefits can include:
- Reduced network requests
- Faster display of previously loaded data
- Better user experience in some scenarios
- Reduced server traffic
Caching strategy should be selected according to the application's data freshness requirements.
Basic Caching Concept
Request Data
↓
Check Cache
↓
Cached Data Available?
/ \
Yes No
↓ ↓
Use API Request
Cache ↓
↓ Save Cache
└─────┬─────
↓
Display
42. Parsing Large JSON Responses
Parsing a very large JSON document can require significant computation. For large responses, expensive JSON parsing can be moved to a separate Dart isolate when appropriate so that UI work is not unnecessarily blocked.
final response = await client.get(
Uri.parse('https://example.com/api/photos'),
);
For large responses, consider separating network retrieval from expensive parsing and using an isolate when appropriate.
Conceptual Flow
Network Response
↓
Large JSON
↓
Background Isolate
↓
JSON Parsing
↓
Structured Data
↓
Main Isolate
↓
UI
43. Using http.Client
Instead of calling top-level methods directly, an application can use an http.Client.
final client = http.Client();
try {
final response = await client.get(
Uri.parse('https://example.com/api/users'),
);
print(response.body);
} finally {
client.close();
}
Using a client can also make networking code easier to test and substitute with different implementations.
Why Use http.Client?
- Provides an explicit HTTP client instance.
- Can be injected into service classes.
- Can make testing easier.
- Allows the client lifecycle to be controlled.
44. Testing HTTP Requests
Networking code should be designed so that it can be tested without depending on a real production server.
A service that accepts an http.Client is easier to replace with a fake or mock client during testing.
class ApiService {
final http.Client client;
ApiService(this.client);
Future<http.Response> getUsers() {
return client.get(
Uri.parse('https://example.com/api/users'),
);
}
}
Testing Architecture
Test
↓
Fake / Mock Client
↓
ApiService
↓
Application Logic
This avoids making unnecessary real network requests during unit tests.
45. Common HTTP Request Mistakes
- Forgetting to add the
http package.
- Forgetting Android internet permission when required.
- Not checking
statusCode.
- Trying to decode invalid JSON.
- Making API requests repeatedly inside
build().
- Hard-coding sensitive credentials in the application.
- Ignoring network exceptions.
- Not displaying a loading state.
- Not providing a retry option after failures.
- Putting all API code directly inside UI widgets.
- Not validating the API response structure.
Common Mistake Example
@override
Widget build(BuildContext context) {
final future = fetchData();
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// UI
},
);
}
Depending on the widget's rebuild behavior, creating the request this way can cause repeated network calls. Store the Future at an appropriate lifecycle point when necessary.
46. Best Practices for HTTP Requests
- Use the
http package for straightforward HTTP communication.
- Use
async and await for asynchronous operations.
- Use
Uri to construct URLs safely.
- Check HTTP status codes.
- Handle exceptions and timeouts.
- Convert JSON into strongly structured Dart models.
- Keep API code separate from UI code.
- Use repositories or service classes in larger applications.
- Do not expose sensitive secrets in client-side code.
- Test networking code using injectable clients or mocks.
- Use pagination for large datasets.
- Consider caching when appropriate.
- Move expensive JSON parsing to an isolate when necessary.
47. HTTP Request Lifecycle
1. User performs an action
↓
2. Flutter creates the request
↓
3. Uri is created
↓
4. HTTP method is selected
↓
5. Headers/body are added
↓
6. Request is sent
↓
7. Server processes request
↓
8. Response is returned
↓
9. Status code is checked
↓
10. JSON is decoded
↓
11. JSON is converted to a model
↓
12. UI state is updated
↓
13. Data is displayed
Complete Architecture Flow
User
↓
Flutter UI
↓
State / ViewModel
↓
Repository
↓
API Service
↓
HTTP Client
↓
REST API
↓
Backend
↓
Database
↓
JSON Response
↓
Model
↓
State Update
↓
Flutter UI
48. Practical Example: User List App
A simple Flutter user-list application can follow this architecture:
Flutter UI
↓
UserViewModel
↓
UserRepository
↓
ApiService
↓
HTTP GET
↓
Backend API
↓
JSON Response
↓
User Model
↓
UI List
Example API Service
class ApiService {
final http.Client client;
ApiService(this.client);
Future<List<User>> fetchUsers() async {
final response = await client.get(
Uri.parse('https://example.com/api/users'),
);
if (response.statusCode != 200) {
throw Exception('Failed to load users');
}
final List<dynamic> data = jsonDecode(response.body);
return data
.map((item) => User.fromJson(item))
.toList();
}
}
Practical UI Concept
Loading
↓
CircularProgressIndicator
Success
↓
ListView
↓
User Cards
Error
↓
Error Message
↓
Retry Button
49. HTTP Request vs HTTP Response
| Request |
Response |
| Sent by Flutter app |
Sent by server |
| Contains URL |
Contains response data |
| Contains HTTP method |
Contains status code |
| Can contain headers |
Can contain headers |
| Can contain a body |
Can contain a body |
Simple Comparison
Request
├── URL
├── Method
├── Headers
└── Body
Response
├── Status Code
├── Headers
└── Body
50. HTTP Methods Quick Revision
| Method |
Purpose |
Flutter Method |
| GET |
Read data |
http.get() |
| POST |
Create/send data |
http.post() |
| PUT |
Update data |
http.put() |
| PATCH |
Partially update data |
http.patch() |
| DELETE |
Delete data |
http.delete() |
51. Mini Project Exercise
Create a Flutter application that retrieves users from an API and displays them in a ListView.
Requirements
- Add the
http package.
- Create a
User model.
- Create an API service.
- Send a GET request.
- Decode JSON.
- Convert JSON into User objects.
- Display users in a ListView.
- Show a loading indicator while the request is running.
- Show an error message when the request fails.
- Add a retry button.
Suggested Project Flow
main.dart
↓
UsersScreen
↓
User State
↓
UserRepository
↓
ApiService
↓
GET /users
↓
JSON
↓
User.fromJson()
↓
List<User>
↓
ListView
52. Practice Questions
- What is an HTTP request?
- What is the difference between a request and a response?
- What is the purpose of the
http package?
- What is the purpose of
http.get()?
- When should you use POST?
- What is the difference between PUT and PATCH?
- What does DELETE do?
- What is an HTTP status code?
- Why should status codes be checked?
- What is JSON?
- What does
jsonDecode() do?
- Why should API data be converted into Dart models?
- What are HTTP headers?
- How are authentication tokens commonly sent?
- Why are async and await used with HTTP requests?
- Why should API calls not normally be triggered directly from
build()?
- What is an API service class?
- What is the repository pattern?
- Why is pagination useful?
- When might JSON parsing need to be moved to another isolate?
53. Interview Questions
Q1. What package is commonly used for HTTP requests in Flutter?
The http package is a common and straightforward option for making HTTP requests in Flutter.
Q2. What does http.get() return?
It returns a Future containing an http.Response.
Q3. Why is async used with HTTP requests?
HTTP communication is asynchronous. The async keyword allows a function to perform asynchronous operations and return a Future.
Q4. Why is await used?
await waits for a Future to complete before continuing with the next statements in the asynchronous function.
Q5. Why should you check statusCode?
The status code indicates how the server handled the request. Checking it allows the application to distinguish successful responses from error responses.
Q6. Why use a model class?
A model class converts raw API data into a structured Dart object, making application code easier to understand and maintain.
Q7. What is the difference between PUT and PATCH?
PUT is commonly used for updating or replacing a resource, while PATCH is commonly used for partial updates. The exact semantics depend on the API design.
Q8. Why should API calls be separated from UI code?
Separating networking logic improves organization, reusability, maintainability, and testability.
Q9. Why should sensitive API secrets not be stored directly in a Flutter application?
Client applications can be inspected. Secrets that must remain private should generally be protected and used on a trusted server-side environment.
Q10. Why is pagination useful?
Pagination allows an API to return large datasets in smaller portions, reducing the amount of data transferred and processed at one time.
54. Official Flutter Networking Resources
55. Learn Flutter with JustAcademy
For structured Flutter training and practical learning, explore the following resources:
JustAcademy Flutter Training Course
Register for Flutter Course Demo
56. Quick Revision
| Concept |
Key Point |
| HTTP |
Protocol used for communication between clients and servers. |
| http Package |
Provides methods for making HTTP requests. |
| GET |
Retrieve data. |
| POST |
Create or submit data. |
| PUT |
Update or replace data. |
| PATCH |
Partially update data. |
| DELETE |
Remove data. |
| Uri |
Represents and constructs URLs. |
| Future |
Represents a value available asynchronously. |
| async/await |
Used to write readable asynchronous code. |
| JSON |
Common format for API data exchange. |
| Model |
Converts API data into structured Dart objects. |
| Headers |
Provide metadata and authentication information. |
| Repository |
Provides an abstraction over data sources. |
| Pagination |
Loads large datasets in smaller portions. |
| http.Client |
Provides an explicit HTTP client that can be injected and tested. |
57. Learning Outcome
After completing this topic, you should be able to:
- Understand HTTP communication in Flutter.
- Explain the difference between HTTP requests and responses.
- Add and use the
http package.
- Create GET, POST, PUT, PATCH, and DELETE requests.
- Construct URLs using
Uri.
- Use query parameters and path parameters.
- Send headers and JSON request bodies.
- Handle authentication headers.
- Understand Futures and asynchronous HTTP operations.
- Decode JSON responses.
- Create Dart model classes.
- Handle loading, success, empty, and error states.
- Use
FutureBuilder appropriately.
- Separate API code from UI code.
- Use service and repository layers.
- Understand basic MVVM networking architecture.
- Handle pagination, caching, retries, and file uploads.
- Design networking code that can be tested.
58. Recommended HTTP Development Process
1. Understand the API documentation
↓
2. Identify endpoint and HTTP method
↓
3. Construct Uri
↓
4. Add required headers
↓
5. Add request body if required
↓
6. Send asynchronous request
↓
7. Apply timeout/error handling
↓
8. Check statusCode
↓
9. Decode JSON
↓
10. Convert JSON to models
↓
11. Update application state
↓
12. Display loading/success/error UI
↓
13. Test the networking layer
59. Summary
HTTP requests allow Flutter applications to communicate with remote APIs and backend services. The http package provides convenient methods such as get(), post(), put(), patch(), and delete().
A typical API workflow involves creating a URI, selecting an HTTP method, adding headers and a request body when required, sending the asynchronous request, waiting for the response, checking the status code, decoding JSON, converting the response into Dart models, and updating the UI.
For production applications, networking code should be separated from UI code, errors and loading states should be handled properly, authentication should be implemented securely, and larger applications can use an appropriate architecture such as service/repository layers or MVVM.
Core concept to remember:
Request
↓
Server
↓
Response
↓
Status Check
↓
JSON Decode
↓
Dart Model
↓
Application State
↓
Flutter UI